package gouciparser

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"

	"gitlab.gbcom.com.cn/tms/gouciparser/log"
)

// 清理临时工作区
func InitTmpWorkspace() error {
	// 判断tmp目录是否存在
	tempDir := GetTempDir()
	_, err := os.Stat(tempDir)
	if err == nil { // 文件或者目录存在，删除
		log.Debug("清空临时工作区 ")
		// windows系统直接调用命令删除tmp目录
		if runtime.GOOS == "windows" {
			cmd := exec.Command("cmd", "/C", "rd", "/S", "/Q", tempDir) // 删除命令
			cmdSysProcAttr(cmd)                                         // 删除时不弹窗dos
			err = cmd.Run()                                             // 执行删除
			if err != nil {
				log.Debug("修改临时工作区文件权限")
				err = fileChmod(tempDir)
				if err != nil {
					log.Error("修改临时工作区文件权限失败，err:%s" + err.Error())
					return err
				}
				err = cmd.Run()
			}
		} else { // 非win系统删除
			err = os.RemoveAll(tempDir)
		}
		if err != nil { // 删除目录失败
			log.Error("清空临时工作区失败，Err:%s ", err.Error())
			return err
		}
		// 删除后在新建目录，检查目录是否存在
		_, err = os.Stat(tempDir)
		if err != nil { // 文件或者目录存在，删除
			if os.IsNotExist(err) {
				return os.Mkdir(tempDir, 0766)
			}
			if os.IsPermission(err) {
				log.Debug("修改文件权限")
				err = fileChmod(tempDir)
				if err != nil {
					return err
				}
				// windows系统直接调用命令删除tmp目录
				if runtime.GOOS == "windows" {
					cmd := exec.Command("cmd", "/C", "rd", "/S", "/Q", tempDir) // 删除命令
					cmdSysProcAttr(cmd)                                         // 删除时不弹窗dos
					err = cmd.Run()                                             // 执行删除
					if err != nil {
						log.Debug("修改临时工作区文件权限")
						err = fileChmod(tempDir)
						if err != nil {
							log.Error("修改临时工作区文件权限失败，err:%s" + err.Error())
							return err
						}
						err = cmd.Run()
					}
				} else { // 非win系统删除
					err = os.RemoveAll(tempDir)
				}
			}
			return err
		}
		return nil
	} else {
		if os.IsNotExist(err) {
			log.Debug("create temp dir")
			return os.Mkdir(tempDir, 0766)
		}
		return err
	}
}

// 配置文件，luci配置文件对象
type ConfFile struct {
	Name    string    // 文件名称
	Configs []*Config // 文件内配置区
}

// 文件内的配置结构体，
type Config struct {
	Type    int           // 配置类型 0空行，1注释，2配置段
	Mark    string        // 配置段标识, type(1)时，为注释
	Name    string        // 配置名称，
	Ops     []*Option     // 属性列表
	Lis     [][]*Option   // 一个配置端中，可能有多个List，每个List可能还有多个值
	Comment CommentInline // 行内注释
}

// config配置区的option属性字段，行内容： option opname  opvalue
type Option struct {
	Type    int // openwrt 配置文件值类型，统一采用字符串处理了，此字段暂不使用
	Name    string
	Value   string        // 值可能是字符串，可能是数字，此处统一采用字符串处理
	Comment CommentInline // 行内注释
}

// 注释
type CommentInline struct {
	Has bool   // 是否有行内注释
	Str string // 行内注释内容
}

// 新增一个配置段
func NewConfig(mark string, name string) *Config {
	c := Config{}
	c.Type = ConfigTypeSection
	c.Mark = strings.TrimSpace(mark)
	c.Name = strings.TrimSpace(name)
	c.Ops = make([]*Option, 0, 10)
	c.Lis = make([][]*Option, 0, 1)
	return &c
}
func (c *Config) Delete(opType int, opName string, opValue string) {
	switch opType {
	case OptionTypeOption:
		c.DelOption(opName, opValue)
	case OptionTypeList:
		c.DelList(opName, opValue)
	}
}
func (c *Config) DelOption(opName string, opValue string) {
	opName = strings.TrimSpace(opName)
	// opValue = strings.TrimSpace(opValue)
	ops := make([]*Option, 0, 5)
	for _, o := range c.Ops {
		if len(opValue) == 0 {
			if o.Name == opName {
				continue
			}
		} else {
			if o.Name == opName && o.Value == opValue {
				continue
			}
		}
		ops = append(ops, o)
	}
	c.Ops = ops
}
func (c *Config) DelList(opName string, opValue string) {
	opName = strings.TrimSpace(opName)
	opValue = strings.TrimSpace(opValue)

	for i, o := range c.Lis {
		lis := make([]*Option, 0, 5)
		for _, l := range o {
			if len(opValue) == 0 {
				if l.Name == opName {
					continue
				}
			} else {
				if l.Name == opName && l.Value == opValue {
					continue
				}
			}
		}
		c.Lis[i] = lis
	}

}
func (c *Config) Set(opType int, opName string, opValue string) {
	switch opType {
	case OptionTypeOption:
		c.SetOption(opName, opValue)
	case OptionTypeList:
		c.SetList(opName, opValue)
	}
}
func (c *Config) SetOption(opName string, opValue string) {
	opName = strings.TrimSpace(opName)
	opValue = strings.TrimSpace(opValue)
	for _, o := range c.Ops {
		if o.Name == opName {
			o.Value = opValue
			return
		}
	}
	op := &Option{
		Type:  OptionTypeOption,
		Name:  opName,
		Value: opValue,
	}
	if c.Ops == nil {
		c.Ops = make([]*Option, 0, 1)
	}
	c.Ops = append(c.Ops, op)
}
func (c *Config) SetOptionAndComment(opName string, opValue string, cmt CommentInline) {
	opName = strings.TrimSpace(opName)
	opValue = strings.TrimSpace(opValue)
	for _, o := range c.Ops {
		if o.Name == opName {
			o.Value = opValue
			return
		}
	}
	op := &Option{
		Type:    OptionTypeOption,
		Name:    opName,
		Value:   opValue,
		Comment: cmt,
	}
	if c.Ops == nil {
		c.Ops = make([]*Option, 0, 1)
	}
	c.Ops = append(c.Ops, op)
}
func (c *Config) SetList(opName string, opValue string) {
	opName = strings.TrimSpace(opName)
	opValue = strings.TrimSpace(opValue)
	for i, o := range c.Lis {
		if len(o) > 0 {
			if o[0].Name == opName {
				op := &Option{Type: OptionTypeList, Name: opName, Value: opValue}
				c.Lis[i] = append(o, op) // o 可能溢出，产生新值
				return
			}
		}
	}
	op := &Option{
		Type:  OptionTypeList,
		Name:  opName,
		Value: opValue,
	}
	lis := []*Option{op}
	if c.Lis == nil {
		c.Lis = make([][]*Option, 0, 1)
	}
	c.Lis = append(c.Lis, lis)
}
func (c *Config) SetListAndComment(opName string, opValue string, cmt CommentInline) {
	opName = strings.TrimSpace(opName)
	opValue = strings.TrimSpace(opValue)
	for i, o := range c.Lis {
		if len(o) > 0 {
			if o[0].Name == opName {
				op := &Option{Type: OptionTypeList, Name: opName, Value: opValue}
				c.Lis[i] = append(o, op) // o 可能溢出，产生新值
				return
			}
		}
	}
	op := &Option{
		Type:    OptionTypeList,
		Name:    opName,
		Value:   opValue,
		Comment: cmt,
	}
	lis := []*Option{op}
	if c.Lis == nil {
		c.Lis = make([][]*Option, 0, 1)
	}
	c.Lis = append(c.Lis, lis)
}

// 依据名称查找是否存在该值
func (c *Config) GetOptionVal(OpKey string) (string, error) {
	for _, v := range c.Ops {
		if v.Name == OpKey {
			return v.Value, nil
		}
	}
	return "", errors.New("not found option")
}

const (
	ConfigTypeNull    = 0 // 空行
	ConfigTypeComment = 1 // 注释
	ConfigTypeSection = 2 // 段配置信息

	OptionTypeOption = 0
	OptionTypeList   = 1
)

// 将行内容包含的注释截取
func trimComment(str string) (line string, comment CommentInline) {
	ok := strings.Contains(str, "#")
	if ok {
		// 包含#
		s := []rune(str) // 字符可能有中文
		for i := 0; i < len(s); i++ {
			if s[i] == '#' {
				line = strings.TrimSpace(string(s[:i]))
				comment = CommentInline{
					Has: true,
					Str: string(s[i:]),
				}
				return
			}
		}
	}

	line = str
	comment = CommentInline{}
	return
}

// 加载文件，解析为config结构体对象,如果不指定path则从默认[系统临时目录/etc/config]目录下加载文件，指定则从指定的[path/etc/config]下加载
func LoadConfig(fileName string, path ...string) *ConfFile {
	fileName = strings.TrimSpace(fileName)
	if len(fileName) == 0 {
		log.Error("parser LoadConfig file name is empty")
		return nil
	}
	_fileName := "etc/config/" + fileName
	dir := GetTempDir()
	if len(path) > 0 {
		dir = path[0]
	}
	fileName = filepath.Join(dir, "etc", "config", fileName)        // 所有的配置文件都在config目录下
	file, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE, 0666) // 打开读写
	if err != nil {
		log.Error("[%s]文件加载失败 >>>>>>>>>>>>", _fileName)
		return nil
	}
	defer file.Close()
	cf := ConfFile{Name: fileName}
	cf.Configs = make([]*Config, 0, 10)
	var currConfig *Config      // 初始，nil
	rd := bufio.NewReader(file) // 将文本内容加载到缓冲区
	loop := true
	for loop {
		line, err := rd.ReadString('\n') //以'\n'为结束符读入一行
		if err != nil {                  // 读取到错误或者结束，退出
			if errors.Is(err, io.EOF) {
				loop = false // 读取结束, 下次循环退出[当前读取的line可能有数据, 继续解析]
			} else {
				log.Error("read err[%s] from file[%s]", err.Error(), fileName)
				break
			}
		}

		// 截取行内注释，#号之后的都是注释
		// 行内容中有tab符号，更换成空格
		// 匹配一个或多个空白符的正则表达式
		regStr := "\\t+" // 规则表达式
		reg := regexp.MustCompile(regStr)
		line = reg.ReplaceAllString(line, " ") // tab符替换为空格
		line = strings.TrimSpace(line)
		if len(line) == 0 { // 空行，跳过
			continue
		}
		if comment := strings.Index(line, "#"); comment == 0 { // 本行为注释
			mc := new(Config)
			mc.Type = ConfigTypeComment
			mc.Mark = line
			cf.Configs = append(cf.Configs, mc)
			continue
		}
		// 检查行内是否有注注释，截取分离配置行信息和注释信息
		line, cmt := trimComment(line)
		// 配置信息
		ss1 := strings.Split(line, " ") // 对行配置信息继续分离
		ss := make([]string, 0, len(ss1))
		for i := 0; i < len(ss1); i++ { // 清除分离字符的空格
			a := strings.TrimSpace(ss1[i])
			if len(a) > 0 {
				ss = append(ss, a)
			}
		}
		// 对有用信息校验
		if len(ss) > 1 { // 配置信息至少两个字符串
			if len(ss) > 3 { // 当name字段的值有空格时，会大于3
				s3 := ss[2]
				for i := 3; i < len(ss); i++ {
					s3 = fmt.Sprintf("%s %s", s3, ss[i])
				}
				s3 = strings.Trim(s3, "'") // 删除引号
				s3 = strings.TrimSpace(s3)
				ss = append(ss[0:2], s3) // ss为新生成的slice，长度为3
			}
			// 此时 len(ss) = 2 or 3
			switch ss[0] {
			case "config": // 遇到新的配置区，新建配置区，并加入到文件结构体列表
				mark := strings.Trim(ss[1], "'")
				name := ""
				if len(ss) > 2 {
					name = strings.Trim(ss[2], "'")
				}
				currConfig = NewConfig(mark, name)
				currConfig.Comment = cmt
				cf.Configs = append(cf.Configs, currConfig)
			case "option":
				if currConfig == nil {
					log.Error("[%s]文件读取到option属性内容格式不正确[缺少config字段]:%s", _fileName, line)
					break
				}
				if currConfig.Ops == nil {
					currConfig.Ops = make([]*Option, 0, 10)
				}
				key := ss[1]
				val := ""
				if len(ss) > 2 {
					val = strings.TrimSpace(ss[2])
					val = strings.Trim(val, "'")
				}
				currConfig.SetOptionAndComment(key, val, cmt)
			case "list":
				if currConfig == nil {
					log.Error("[%s]文件读取到list属性内容格式不正确[缺少config字段]:%s", _fileName, line)
					break
				}
				if len(ss) != 3 {
					log.Error("[%s]:%s >>>>>>>>>>>>", _fileName, line)
					break
				}
				if currConfig.Lis == nil {
					currConfig.Lis = make([][]*Option, 0, 2)
				}
				// key := ss[1]
				// val := ""
				// if len(ss) > 2 {
				// 	val = ss[2]
				// }
				// currConfig.SetListAndComment(key, val, cmt)
				if len(currConfig.Lis) > 0 {
					has := false
					for i := 0; i < len(currConfig.Lis); i++ {
						if len(currConfig.Lis[i]) > 0 {
							if currConfig.Lis[i][0].Name == strings.Trim(ss[1], "'") {
								op := new(Option)
								op.Comment = cmt
								op.Name = strings.Trim(ss[1], "'")
								op.Name = strings.TrimSpace(op.Name)
								op.Value = strings.Trim(ss[2], "'")
								op.Value = strings.TrimSpace(op.Value)
								op.Type = OptionTypeList
								currConfig.Lis[i] = append(currConfig.Lis[i], op)
								has = true
							}
						}
					}
					if !has {
						ops := make([]*Option, 0, 5)
						op := new(Option)
						op.Comment = cmt
						op.Name = strings.Trim(ss[1], "'")
						op.Name = strings.TrimSpace(op.Name)
						op.Value = strings.Trim(ss[2], "'")
						op.Value = strings.TrimSpace(op.Value)
						op.Type = OptionTypeList
						ops = append(ops, op)
						currConfig.Lis = append(currConfig.Lis, ops)
					}
				} else {
					// 第一次添加,len =0
					ops := make([]*Option, 0, 5)
					op := new(Option)
					op.Comment = cmt
					op.Name = strings.Trim(ss[1], "'")
					op.Name = strings.TrimSpace(op.Name)
					op.Value = strings.Trim(ss[2], "'")
					op.Value = strings.TrimSpace(op.Value)
					op.Type = OptionTypeList
					ops = append(ops, op)
					currConfig.Lis = append(currConfig.Lis, ops)
				}
			default:
				log.Error("[%s]文件读取到不能解析的内容:%s", _fileName, line)
			}
		}
	}
	log.Info("加载 %s 配置模板文件成功", _fileName)
	return &cf
}

func (cf *ConfFile) StoreConfig() error {
	fname := log.GetFileNameFromPath(cf.Name)
	f, err := os.OpenFile(cf.Name, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
	if err == nil {
		defer f.Close()
		f.WriteString(string(cf.bytes()))
		log.Info("保存 %s 配置成功", fname)
		return nil
	}
	log.Error("保存 %s 配置失败,Err:%s", fname, err.Error())
	return err
}

// 将配置模型输出到文件，换行符只能用\n,使用\r\n作为换行符，openwrt解析配置文件错误，会反复重启
func (cf *ConfFile) bytes() []byte {
	var buf bytes.Buffer
	for _, v := range cf.Configs {
		buf.WriteString("\n")
		if v.Type == ConfigTypeNull {
			// buf.WriteString("\n")
			continue
		}
		if v.Type == ConfigTypeComment {
			buf.WriteString(v.Mark)
			// buf.WriteString("\n")
			continue
		}
		if v.Name == "" {
			buf.WriteString(fmt.Sprintf("config %s", v.Mark))
		} else {
			buf.WriteString(fmt.Sprintf("config %s '%s'", v.Mark, v.Name))
		}
		if v.Comment.Has { // config 有行内注释的话
			buf.WriteString(fmt.Sprintf("\t\t%s", v.Comment.Str))
		}

		buf.WriteString("\n")
		if v.Ops != nil && len(v.Ops) > 0 {
			for _, op := range v.Ops {
				buf.WriteString(fmt.Sprintf("\toption %-20s\t'%s'", op.Name, op.Value))
				if op.Comment.Has { // option 有行内注释
					buf.WriteString(fmt.Sprintf("\t\t%s", op.Comment.Str))
				}
				buf.WriteString("\n")
			}
		}
		if v.Lis != nil && len(v.Lis) > 0 {
			for _, lis := range v.Lis {
				for _, li := range lis {
					buf.WriteString(fmt.Sprintf("\tlist %-20s\t'%s'", li.Name, li.Value))
					if li.Comment.Has { // list 有行内注释
						buf.WriteString(fmt.Sprintf("\t\t%s", li.Comment.Str))
					}
					buf.WriteString("\n")
				}
			}
		}
	}
	return buf.Bytes()
}

// 段中查询字段修改，未找到则添加
func (cf *ConfFile) Set(sectionMark string, sectionName string, opType int, opName string, opValue string) {
	hasSec := false // 段是否存在
	if cf.Configs == nil {
		cf.Configs = make([]*Config, 0, 1)
	}
	for _, v := range cf.Configs {
		if v.Type == ConfigTypeSection {
			if v.Name == sectionName && v.Mark == sectionMark { // 只匹配了第一个相同的section name
				hasSec = true
				v.Set(opType, opName, opValue)
				break
			}
		}
	}
	if !hasSec {
		c := NewConfig(sectionMark, sectionName)
		c.Set(opType, opName, opValue)
		cf.Configs = append(cf.Configs, c)
	}
}

// 匿名节点修改时，应全部删除再append进来
func (cf *ConfFile) DeleteSection(sectionMark, sectionName string) {
	cs := make([]*Config, 0, len(cf.Configs)+5)
	for i := 0; i < len(cf.Configs); i++ {
		if sectionName == cf.Configs[i].Name && sectionMark == cf.Configs[i].Mark {
			continue
		}
		cs = append(cs, cf.Configs[i])
	}
	cf.Configs = cs
}

// 匿名节点修改时，应全部删除再append进来
func (cf *ConfFile) DeleteOption(sectionMark string, sectionName string, opType int, opName string, opValue string) {
	for i := 0; i < len(cf.Configs); i++ {
		if sectionName == cf.Configs[i].Name && sectionMark == cf.Configs[i].Mark {
			cf.Configs[i].Delete(opType, opName, opValue)
		}
	}
}

// 依据给定的secName,secVal,opNanme ,opValue查找config段
func (cf *ConfFile) FindConfigByOption(seckey, secval, opName, opVale string) *Config {
	for i := 0; i < len(cf.Configs); i++ {
		if cf.Configs[i].Type == ConfigTypeSection {
			if cf.Configs[i].Mark == seckey && cf.Configs[i].Name == secval {
				for _, v := range cf.Configs[i].Ops {
					if v.Name == opName && v.Value == opVale {
						return cf.Configs[i]
					}
				}
			}
		}
	}
	return nil
}

// 依据给定secKey\secVal，查询配置段
func (cf *ConfFile) GetConfig(seckey, secval string) *Config {
	for i := 0; i < len(cf.Configs); i++ {
		if cf.Configs[i].Type == ConfigTypeSection {
			if cf.Configs[i].Mark == seckey && cf.Configs[i].Name == secval {
				return cf.Configs[i]
			}
		}
	}
	return nil
}

func GetTempDir() string {
	tempDirRoot := os.TempDir()
	return filepath.Join(tempDirRoot, "progen_temp")
}

// 查询option, key的val
func (cfg *ConfFile) GetOptionVal(secKey string, secName string, opName string) string {
	val := ""
	for _, cf := range cfg.Configs {
		if cf.Mark == secKey && cf.Name == secName {
			for _, op := range cf.Ops {
				if op.Name == opName {
					val = op.Value
					break
				}
			}
			break
		}
	}
	return val
}

// 查询option, key的val
func (cfg *ConfFile) GetOptionValDefault(secKey string, secName string, opName string, dft ...string) string {
	val := ""
	for _, cf := range cfg.Configs {
		if cf.Mark == secKey && cf.Name == secName {
			for _, op := range cf.Ops {
				if op.Name == opName {
					val = op.Value
					break
				}
			}
			break
		}
	}
	if len(val) == 0 && len(dft) > 0 {
		return dft[0]
	}
	return val
}

// 查询option, key的val
func (cfg *ConfFile) GetOptionValMustBool(secKey string, secName string, opName string) bool {
	v := cfg.GetOptionVal(secKey, secName, opName)
	v = strings.ToLower(v)
	if v == "1" || v == "t" || v == "y" || v == "yes" || v == "true" || v == "on" {
		return true
	}
	return false
}

// 查询option, key的val
func (cfg *ConfFile) GetOptionValMustInt(secKey string, secName string, opName string, dft ...int) int {
	v := cfg.GetOptionVal(secKey, secName, opName)
	i, err := strconv.Atoi(v)
	if err != nil {
		dft = append(dft, 0)
		return dft[0]
	}
	return i
}
